#ifdef FCEUDEF_DEBUGGER
#include "common.h"
#include "../../x6502.h"
#include "../../debug.h"
#include "../../ppuview.h"
#include "debug.h"
#include "../../ppu.h"

static void DoMemmo(HWND hParent);
void UpdateDebugger(void);
//static int discallb(uint16 a, char *s);
static void crs(HWND hParent);
static void Disyou(uint16 a);
static void UpdateDMem(uint16 a);

static HWND mwin = 0;
static int32 cmsi;
static int instep = -1;
static X6502 *Xsave;

extern uint32 RefreshAddr;

HWND dwin;

static uint16 StrToU16(char *s) {
	unsigned int ret = 0;
	sscanf(s, "%4x", &ret);
	return ret;
}

static uint8 StrToU8(char *s) {
	unsigned int ret = 0;
	sscanf(s, "%2x", &ret);
	return ret;
}

/* Need to be careful where these functions are used. */
static char *U16ToStr(uint16 a) {
	static char TempArray[16];
	sprintf(TempArray, "%04X", a);
	return TempArray;
}

static char *U8ToStr(uint8 a) {
	static char TempArray[16];
	sprintf(TempArray, "%02X", a);
	return TempArray;
}

static uint16 asavers[27];


static void crs(HWND hParent) {
// SetDlgItemText(hParent,320,(LPTSTR)(instep>=0)?"Stopped.":"Running...");
	EnableWindow(GetDlgItem(hParent, 301), (instep >= 0) ? 1 : 0);
}

static void Disyou(uint16 a) {
	int discount;

	SendDlgItemMessage(dwin, 100, LB_RESETCONTENT, 0, 0);

	discount = 0;
	while (discount < 25) {
		char buffer[256];
		uint16 prea = a;

		asavers[discount] = a;
		sprintf(buffer, "%04x: ", a);

		a = FCEUI_Disassemble(Xsave, a, buffer + strlen(buffer));
		if (prea == Xsave->PC)
			SendDlgItemMessage(dwin, 100, LB_SETCURSEL, discount, 0);

		SendDlgItemMessage(dwin, 100, LB_ADDSTRING, 0, (LPARAM)(LPSTR)buffer);
		discount++;
	}
	//FCEUI_Disassemble(Xsave,a,discallb);
}

static void SSI(X6502 *X) {
	SCROLLINFO si;

	memset(&si, 0, sizeof(si));
	si.cbSize = sizeof(si);
	si.fMask = SIF_ALL;
	si.nMin = -32768;
	si.nMax = 32767;
	si.nPage = 27;
	si.nPos = X->PC - 32768;
	SetScrollInfo(GetDlgItem(dwin, 101), SB_CTL, &si, 1);
}

static void RFlags(X6502 *X) {
	int x;

	SetDlgItemText(dwin, 202, (LPTSTR)U8ToStr(X->P & ~0x30));
	for (x = 0; x < 8; x++) {
		if (x != 4 && x != 5)
			CheckDlgButton(dwin, 400 + x, ((X->P >> x) & 1) ? BST_CHECKED : BST_UNCHECKED);
	}
}

static void RRegs(X6502 *X) {
	SetDlgItemText(dwin, 200, (LPTSTR)U16ToStr(X->PC));
	SetDlgItemText(dwin, 201, (LPTSTR)U8ToStr(X->S));
	SetDlgItemText(dwin, 210, (LPTSTR)U8ToStr(X->A));
	SetDlgItemText(dwin, 211, (LPTSTR)U8ToStr(X->X));
	SetDlgItemText(dwin, 212, (LPTSTR)U8ToStr(X->Y));
	SetDlgItemText(dwin, 223, (LPTSTR)U16ToStr(RefreshAddr));
}

static void DoVecties(HWND hParent) {
	uint16 m[3];
	int x;

	FCEUI_GetIVectors(&m[2], &m[0], &m[1]);
	for (x = 0; x < 3; x++)
		SetDlgItemText(hParent, 220 + x, (LPTSTR)U16ToStr(m[x]));
}

static void Redrawsy(X6502 *X) {
	SSI(X);
	RFlags(X);
	RRegs(X);
	Disyou(X->PC);
	DoVecties(dwin);
}

int BlockingCheck(void);

static void MultiCB(X6502 *X) {
	Xsave = X;
	if (instep >= 0) {
		Redrawsy(X);
		if (mwin)
			UpdateDMem(cmsi);
	}

	while (instep >= 0) {
		if (instep >= 1) {
			instep--;
			return;
		}
		StopSound();
		if (!BlockingCheck()) {       /* Whoops, need to exit for some reason. */
			instep = -1;
			FCEUI_SetCPUCallback(0);
			return;
		}
		Sleep(50);
	}
}

static int multistep = 0;
static void cpucb(X6502 *X) {
	multistep = 0;
	MultiCB(X);
}

static void BPointHandler(X6502 *X, int type, unsigned int A) {
	if (multistep) return;  /* To handle instructions that can cause multiple
						   breakpoints per instruction.
						*/
	if (!dwin) return;  /* Window is closed, don't do breakpoints. */
	multistep = 1;
	instep = 0;
	crs(dwin);
	MultiCB(X);
}

static HWND hWndCallB;
static int RBPCallBack(int type, unsigned int A1, unsigned int A2,
					   void (*Handler)(X6502 *, int type, unsigned int A)) {
	char buf[128];
	sprintf(buf, "%s%s%s%s $%04x - $%04x", (type & BPOINT_READ) ? "R" : " ",
			(type & BPOINT_WRITE) ? "W" : " ", (type & BPOINT_PC) ? "P" : " ", (type & BPOINT_EXCLUDE) ? "X" : " ", A1, A2);
	SendDlgItemMessage(hWndCallB, 510, LB_ADDSTRING, 0, (LPARAM)(LPSTR)buf);
	return(1);
}

static void RebuildBPointList(HWND hwndDlg) {
	SendDlgItemMessage(hwndDlg, 510, LB_RESETCONTENT, 0, 0);
	hWndCallB = hwndDlg;
	FCEUI_ListBreakPoints(RBPCallBack);
}

static void FetchBPDef(HWND hwndDlg, uint16 *A1, uint16 *A2, int *type) {
	char tmp[256];

	GetDlgItemText(hwndDlg, 520, tmp, 256);
	*A1 = StrToU16(tmp);
	GetDlgItemText(hwndDlg, 521, tmp, 256);
	if (tmp[0] == 0)
		*A2 = *A1;
	else
		*A2 = StrToU16(tmp);

	*type = 0;
	*type |= (IsDlgButtonChecked(hwndDlg, 530) == BST_CHECKED) ? BPOINT_READ : 0;
	*type |= (IsDlgButtonChecked(hwndDlg, 531) == BST_CHECKED) ? BPOINT_WRITE : 0;
	*type |= (IsDlgButtonChecked(hwndDlg, 532) == BST_CHECKED) ? BPOINT_PC : 0;
	*type |= (IsDlgButtonChecked(hwndDlg, 533) == BST_CHECKED) ? BPOINT_EXCLUDE : 0;
}

//static void SetBPDef(HWND hwndDlg, int32 A1, int32 A2, int type)
//{
// if(A1>=0)
//  //SetDlgItemText(hwndDlg,520,(LPTSTR)U16ToStr(A1));
// if(A2>=0)
//  SetDlgItemText(hwndDlg,521,(LPTSTR)U16ToStr(A2));
// if(type>=0)
// {
// CheckDlgButton(hwndDlg,123,(soundoptions&SO_SECONDARY)?BST_CHECKED:BST_UNCHECKED);
// }
//}

static BOOL CALLBACK DebugCon(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
	int32 scrollindex = 0, ced = 0;
	int type;
	DSMFix(uMsg);
	switch (uMsg) {
	case WM_INITDIALOG:
		crs(hwndDlg);
		DoVecties(hwndDlg);
		FCEUI_SetCPUCallback(cpucb);
		RebuildBPointList(hwndDlg);
		break;
	case WM_VSCROLL:
		if (0 != instep) break;
		switch ((int)LOWORD(wParam)) {
		case SB_TOP:
			scrollindex = -32768;
			ced = 1;
			break;
		case SB_BOTTOM:
			scrollindex = 32767;
			ced = 1;
			break;
		case SB_LINEUP:
			scrollindex = GetScrollPos(GetDlgItem(hwndDlg, 101), SB_CTL);
			if (scrollindex > -32768) {
				scrollindex--;
				ced = 1;
			}
			break;

		case SB_PAGEUP:
			scrollindex = GetScrollPos(GetDlgItem(hwndDlg, 101), SB_CTL);
			scrollindex -= 27;
			if (scrollindex < -32768)
				scrollindex = -32768;
			ced = 1;
			break;

		case SB_LINEDOWN:
			scrollindex = GetScrollPos(GetDlgItem(hwndDlg, 101), SB_CTL);
			if (scrollindex < 32767) {
				scrollindex++;
				ced = 1;
			}
			break;

		case SB_PAGEDOWN:
			scrollindex = GetScrollPos(GetDlgItem(hwndDlg, 101), SB_CTL);
			scrollindex += 27;
			if (scrollindex > 32767) {
				scrollindex = 32767;
			}
			ced = 1;
			break;

		case SB_THUMBPOSITION:
		case SB_THUMBTRACK:
			scrollindex = (int16)(wParam >> 16);
			ced = 1;
			break;
		}
		if (ced) {
			SendDlgItemMessage(hwndDlg, 101, SBM_SETPOS, scrollindex, 1);
			Disyou(scrollindex + 32768);
		}
		break;


	case WM_CLOSE:
	case WM_QUIT: goto gornk;
	case WM_COMMAND:
		switch (HIWORD(wParam)) {
		case LBN_DBLCLK:
//                         if(0==instep)
//                         {
//                          Xsave->PC=asavers[SendDlgItemMessage(hwndDlg,100,LB_GETCURSEL,0,0)];
//                          RRegs(Xsave);
//                          Disyou(Xsave->PC);
//                         }
//                         break;
			if ((0 == instep) && (LOWORD(wParam) == 0x64)) {
				uint16 A1, A2;
				type = BPOINT_READ | BPOINT_PC;
				A1 = A2 = asavers[SendDlgItemMessage(hwndDlg, 100, LB_GETCURSEL, 0, 0)];
				FCEUI_AddBreakPoint(type, A1, A2, BPointHandler);
				hWndCallB = hwndDlg;            /* Hacky hacky. */
				RBPCallBack(type, A1, A2, 0);
			}
			break;
		case EN_KILLFOCUS:
		{
			char TempArray[64];
			int id = LOWORD(wParam);
			GetDlgItemText(hwndDlg, id, TempArray, 64);

			if (0 == instep) {
				int tscroll = 32768 + SendDlgItemMessage(hwndDlg, 101, SBM_GETPOS, 0, 0);

				switch (id) {
				case 200:
					if (Xsave->PC != StrToU16(TempArray)) {
						Xsave->PC = StrToU16(TempArray);
						Disyou(Xsave->PC);
						RRegs(Xsave);
					}
					break;
				case 201: Xsave->S = StrToU8(TempArray);
					RRegs(Xsave);
					Disyou(tscroll);
					break;
				case 202: Xsave->P &= 0x30;
					Xsave->P |= StrToU8(TempArray) & ~0x30;
					RFlags(Xsave);
					Disyou(tscroll);
					break;
				case 210: Xsave->A = StrToU8(TempArray);
					RRegs(Xsave);
					Disyou(tscroll);
					break;
				case 211: Xsave->X = StrToU8(TempArray);
					RRegs(Xsave);
					Disyou(tscroll);
					break;
				case 212: Xsave->Y = StrToU8(TempArray);
					RRegs(Xsave);
					Disyou(tscroll);
					break;
				}
			}
		}
		break;

		case BN_CLICKED:
			if (LOWORD(wParam) >= 400 && LOWORD(wParam) <= 407) {
				if (0 == instep) {
					Xsave->P ^= 1 << (LOWORD(wParam) & 7);
					RFlags(Xsave);
				}
			}else switch (LOWORD(wParam)) {
				case 300:
					instep = 1;
					crs(hwndDlg);
					if ((PPUViewer) && (scanline == PPUViewScanline)) UpdatePPUView(1);
					break;
				case 301:
					instep = -1;
					crs(hwndDlg);
					break;
				case 302: FCEUI_NMI(); break;
				case 303: FCEUI_IRQ(); break;
				case 310: FCEUI_ResetNES();
					if (instep >= 0)
						instep = 1;
					crs(hwndDlg);
					break;
				case 311: DoMemmo(hwndDlg); break;

				case 540:
				{
					LONG t;
					t = SendDlgItemMessage(hwndDlg, 510, LB_GETCURSEL, 0, 0);
					if (t != LB_ERR) {
						FCEUI_DeleteBreakPoint(t);
						SendDlgItemMessage(hWndCallB, 510, LB_DELETESTRING, t, (LPARAM)(LPSTR)0);
					}
				}
				break;
				case 541:
				{
					uint16 A1, A2;
					int type;
					FetchBPDef(hwndDlg, &A1, &A2, &type);
					FCEUI_AddBreakPoint(type, A1, A2, BPointHandler);
					hWndCallB = hwndDlg;                /* Hacky hacky. */
					RBPCallBack(type, A1, A2, 0);
				}
				break;
				}
			break;
		}


		if (!(wParam >> 16))
			switch (wParam & 0xFFFF) {
			case 1:
 gornk:
				instep = -1;
				FCEUI_SetCPUCallback(0);
				DestroyWindow(dwin);
				dwin = 0;
				break;
			}
	}
	return 0;
}

void BeginDSeq(HWND hParent) {
	if (dwin) {
		SetFocus(dwin);
		return;
	}
	if (!GI) {
		FCEUD_PrintError("You must have a game loaded before you can screw up a game.");
		return;
	}

	dwin = CreateDialog(fceu_hInstance, "DEBUGGER", 0, DebugCon);
}

/* 16 numbers per line times 3 then minus one(no space at end) and plus
   6 for "8000: "-like string and plus 2 for crlf.  *16 for 16 lines, and
   +1 for a null.
*/
static uint8 kbuf[(16 * 3 - 1 + 6 + 2) * 16 + 1];
static void sexycallb(uint16 a, uint8 v) {
	if ((a & 15) == 15)
		sprintf(kbuf + strlen(kbuf), "%02X\r\n", v);
	else if ((a & 15) == 0)
		sprintf(kbuf + strlen(kbuf), "%03xx: %02X ", a >> 4, v);
	else
		sprintf(kbuf + strlen(kbuf), "%02X ", v);
}

static void MDSSI(void) {
	SCROLLINFO si;

	memset(&si, 0, sizeof(si));
	si.cbSize = sizeof(si);
	si.fMask = SIF_ALL;
	si.nMin = 0;
	si.nMax = 0xFFFF >> 4;
	si.nPage = 16;
	cmsi = si.nPos = 0;
	SetScrollInfo(GetDlgItem(mwin, 103), SB_CTL, &si, 1);
}

static void UpdateDMem(uint16 a) {
	kbuf[0] = 0;
	FCEUI_MemDump(a << 4, 256, sexycallb);
	SetDlgItemText(mwin, 100, kbuf);
}

int CreateDumpSaveVid(uint32 a1, uint32 a2) {
	const char filter[] = "Raw dump(*.chr)\0*.chr\0";
	char nameo[2048];
	OPENFILENAME ofn;

	memset(&ofn, 0, sizeof(ofn));
	ofn.lStructSize = sizeof(ofn);
	ofn.hInstance = fceu_hInstance;
	ofn.lpstrTitle = "Dump Video Memory As...";
	ofn.lpstrFilter = filter;
	nameo[0] = 0;
	ofn.lpstrFile = nameo;
	ofn.nMaxFile = 256;
	ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
	if (GetSaveFileName(&ofn)) {
		FCEUI_DumpVid(nameo, a1, a2);
		return(1);
	}
	return 0;
}

int CreateDumpSave(uint32 a1, uint32 a2) {
	const char filter[] = "Raw dump(*.dmp)\0*.dmp\0";
	char nameo[2048];
	OPENFILENAME ofn;

	memset(&ofn, 0, sizeof(ofn));
	ofn.lStructSize = sizeof(ofn);
	ofn.hInstance = fceu_hInstance;
	ofn.lpstrTitle = "Dump Memory As...";
	ofn.lpstrFilter = filter;
	nameo[0] = 0;
	ofn.lpstrFile = nameo;
	ofn.nMaxFile = 256;
	ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
	if (GetSaveFileName(&ofn)) {
		FCEUI_DumpMem(nameo, a1, a2);
		return(1);
	}
	return 0;
}

int LoadSave(uint32 a) {
	const char filter[] = "Raw dump(*.dmp)\0*.dmp\0";
	char nameo[2048];
	OPENFILENAME ofn;

	memset(&ofn, 0, sizeof(ofn));
	ofn.lStructSize = sizeof(ofn);
	ofn.hInstance = fceu_hInstance;
	ofn.lpstrTitle = "Load Memory...";
	ofn.lpstrFilter = filter;
	nameo[0] = 0;
	ofn.lpstrFile = nameo;
	ofn.nMaxFile = 256;
	ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
	if (GetOpenFileName(&ofn)) {
		FCEUI_LoadMem(nameo, a, 0); /* LL Load */
		return(1);
	}
	return 0;
}

static BOOL CALLBACK MemCon(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
	int32 scrollindex = 0, ced = 0;
	char TempArray[64];

	DSMFix(uMsg);

	switch (uMsg) {
	case WM_CLOSE:
	case WM_QUIT: goto gornk;

	case WM_COMMAND:
		switch (HIWORD(wParam)) {
		case BN_CLICKED:
			switch (LOWORD(wParam)) {
			case 203:
			case 202:
			{
				uint16 a;
				uint8 v;
				GetDlgItemText(hwndDlg, 200, TempArray, 64);
				a = StrToU16(TempArray);
				GetDlgItemText(hwndDlg, 201, TempArray, 64);
				v = StrToU8(TempArray);
				FCEUI_MemPoke(a, v, LOWORD(wParam) & 1);
				UpdateDMem(cmsi);

				if (dwin && 0 == instep) {
					int tscroll = 32768 + SendDlgItemMessage(dwin, 101, SBM_GETPOS, 0, 0);
					Disyou(tscroll);
				}
			}
			break;
			case 212:
			{
				uint16 a1;
				uint16 a2;
				GetDlgItemText(hwndDlg, 210, TempArray, 64);
				a1 = StrToU16(TempArray);
				GetDlgItemText(hwndDlg, 211, TempArray, 64);
				a2 = StrToU16(TempArray);
				CreateDumpSave(a1, a2);
			}
			break;
			case 232:
			{
				uint16 a1;
				uint16 a2;
				GetDlgItemText(hwndDlg, 230, TempArray, 64);
				a1 = StrToU16(TempArray);
				GetDlgItemText(hwndDlg, 231, TempArray, 64);
				a2 = StrToU16(TempArray);
				CreateDumpSaveVid(a1, a2);
			}
			break;
			case 222:
			{
				uint16 a;
				GetDlgItemText(hwndDlg, 220, TempArray, 64);
				a = StrToU16(TempArray);
				LoadSave(a);
				UpdateDMem(cmsi);
				if (dwin && 0 == instep) {
					int tscroll = 32768 + SendDlgItemMessage(dwin, 101, SBM_GETPOS, 0, 0);
					Disyou(tscroll);
				}
			}
			break;
			}
			break;
		}

		if (!(wParam >> 16))
			switch (wParam & 0xFFFF) {
			case 1:
 gornk:
				DestroyWindow(mwin);
				mwin = 0;
				break;
			}
		break;
	case WM_INITDIALOG:
		return(1);
	case WM_VSCROLL:
		switch ((int)LOWORD(wParam)) {
		case SB_TOP:
			scrollindex = 0;
			ced = 1;
			break;
		case SB_BOTTOM:
			scrollindex = 0xffff >> 4;
			ced = 1;
			break;
		case SB_LINEUP:
			scrollindex = GetScrollPos(GetDlgItem(hwndDlg, 103), SB_CTL);
			if (scrollindex > 0) {
				scrollindex--;
				ced = 1;
			}
			break;

		case SB_PAGEUP:
			scrollindex = GetScrollPos(GetDlgItem(hwndDlg, 103), SB_CTL);
			scrollindex -= 16;
			if (scrollindex < 0)
				scrollindex = 0;
			ced = 1;
			break;

		case SB_LINEDOWN:
			scrollindex = GetScrollPos(GetDlgItem(hwndDlg, 103), SB_CTL);
			if (scrollindex < (0xFFFF >> 4)) {
				scrollindex++;
				ced = 1;
			}
			break;

		case SB_PAGEDOWN:
			scrollindex = GetScrollPos(GetDlgItem(hwndDlg, 103), SB_CTL);
			scrollindex += 16;
			if (scrollindex > (0xFFFF >> 4)) {
				scrollindex = (65535 >> 4);
			}
			ced = 1;
			break;

		case SB_THUMBPOSITION:
		case SB_THUMBTRACK:
			scrollindex = HIWORD(wParam);
			ced = 1;
			break;
		}
		if (ced) {
			SendDlgItemMessage(hwndDlg, 103, SBM_SETPOS, scrollindex, 1);
			UpdateDMem(scrollindex);
			cmsi = scrollindex;
		}
		break;
	}

	return 0;
}

static void DoMemmo(HWND hParent) {
	if (mwin) {
		SetFocus(mwin);
		return;
	}
	mwin = CreateDialog(fceu_hInstance, "MEMVIEW", 0, MemCon);
	MDSSI();
	UpdateDMem(0);
}

void UpdateDebugger(void) {
	if (mwin)
		UpdateDMem(cmsi);
}

void KillDebugger(void) {
	if (mwin)
		DestroyWindow(mwin);
	if (dwin) {
		instep = -1;
		FCEUI_SetCPUCallback(0);
		DestroyWindow(dwin);
	}
	dwin = mwin = 0;
}
#endif
